package elEMAS.impl;

import elEMAS.inf.Agent;
import elEMAS.inf.Island;

import java.util.List;
import java.util.Random;

import common.BasicAgent;
import common.GeneticHelper;
import common.GeneticOperations;


public class EmasAgent <Genotype> implements Agent<Genotype> {
   
	protected Island<Genotype> _homeIsland;
	protected int _energy;
	
	protected GeneticOperations<Genotype> go;
	private Genotype genotype;
	private List<Double> values;
	private List<Double> fitnesses;

	protected List<Island<Genotype>> _islands; 

    private EmasParameters params;

	public EmasAgent(Island<Genotype> aI, Genotype aGenotype, GeneticOperations<Genotype> aGo, List<Island<Genotype>> aAvailableIslands, EmasParameters params) {
		
	    this.go = aGo;
	    this.genotype = aGenotype;
	    this._homeIsland = aI;
	    this.params = params;
	    this._energy = params.startAgentEnergy;
	    
		this._islands = aAvailableIslands;
		this._homeIsland.addAgent(this);
		
		this.values = go.getValues(this.genotype);
		this.fitnesses = go.getFitnessesFromValue(this.values);

		//System.out.println("Jestem nowym agentem o energii " + this._energy);
	}

	protected void die() {
		//usuniecie sie ze swojej wyspy i innych agentow
	    //System.out.println("die");
	    ElEmasAlgorithm.stat_numOfDeaths++;
		this._homeIsland.removeAgent(this);
	}

	//do innego agenta
	public void transfer(Agent<Genotype> aToAgent) {
	    if (this._energy > 0) {
    		this._energy -= params.basicEnergyQuantum;
    		aToAgent.assimilateEnergy();
	    }
	}

	@Override
    public void transfer(Agent<Genotype> aTarget, int amount) {
	    this._energy -= amount;
	    aTarget.assimilateEnergy(amount);
    }

    public void transferToEnv(int aEnergy) {
        assert this._energy >= 0;
		this._energy -= aEnergy;
		assert this._energy >= 0;
		this._homeIsland.increaseEnergy(aEnergy);
	}
	
    public void create(Agent<Genotype> aPartner) {
		
        Agent<Genotype> newAgent1 = null;
		Agent<Genotype> newAgent2 = null;

		List<Genotype> childGenotypes = go.crossover(this.getGenotype(), aPartner.getGenotype());
		if (childGenotypes.size() == 1) {
		    childGenotypes.addAll(go.crossover(aPartner.getGenotype(), this.getGenotype()));
		}
		
		newAgent1 = new EmasAgent<Genotype>(this._homeIsland, childGenotypes.get(0), this.go, this._islands, this.params);
		newAgent1.mut();
		newAgent2 = new EmasAgent<Genotype>(this._homeIsland, childGenotypes.get(1), this.go, this._islands, this.params);
		newAgent2.mut();
		
		this.loseEnergy(params.startAgentEnergy);
		aPartner.loseEnergy(params.startAgentEnergy);
		
        ElEmasAlgorithm.stat_numOfBornAgents++;
        ElEmasAlgorithm.stat_numOfBornAgents++;
	}

	public void mut() {
	    
	    go.mutate(this.genotype);
	    calculateValues();
	    
	}

	private void calculateValues() {
        this.values = go.getValues(this.genotype);
        this.fitnesses = go.getFitnessesFromValue(this.values);
    }

    protected void move(Island<Genotype> aTarget) {
	    assert aTarget != this._homeIsland;
		aTarget.addAgent(this);
		this._homeIsland.removeAgent(this);
		this._homeIsland = aTarget;
	}

    @Override
	public int checkDomination(BasicAgent<Genotype> aAgent) {
        
        return GeneticHelper.checkDomination(this, aAgent);
	}

	protected Island<Genotype> seekMigr() {
	    
		Random rand = new Random();
		Island<Genotype> targetIsland = this._homeIsland;
		while (targetIsland == this._homeIsland) {
		    int islandNum = rand.nextInt(this._islands.size());
		    targetIsland = this._islands.get(islandNum);
		}
		return targetIsland;
	}

	/*
    protected Agent seekRepro() {
        
    	Random rand = new Random();
    	
    	// usuwam tych, którzy mają za mało energii do reprodukcji (żeby cześciej dochodziło do reprodukcji)
    	List<Agent> reproductableAgents = new LinkedList<Agent>(this._neighbors);
    	Iterator<Agent> it = reproductableAgents.iterator();
    	while( it.hasNext() ) {
    	  Agent foo = it.next();
    	  if( !(foo.getEnergy() > _reproductionThreshold) ) it.remove();
    	}
    	
    	Agent partner;
    	if (reproductableAgents.size() > 0) {
    		int partnerNum = rand.nextInt(reproductableAgents.size());
    		partner = reproductableAgents.get(partnerNum);
    	}
    	else {
    	    int partnerNum = rand.nextInt(this._neighbors.size());
    	    partner = this._neighbors.get(partnerNum);
    	}
    
    	return partner;
    }
    */
	
	private Agent<Genotype> getRandomNeighbour() {
	    
        Random rand = new Random();
        Agent<Genotype> partner = _homeIsland.getAgents().get(rand.nextInt(_homeIsland.getAgents().size()));
        while (partner == this) {
            partner = _homeIsland.getAgents().get(rand.nextInt(_homeIsland.getAgents().size()));
        }
        return partner; 
	}
	
    public Agent<Genotype> seekRepro() {
        
        return getRandomNeighbour();
    }

    public Agent<Genotype> seekMeet() {
        
    	return getRandomNeighbour();
    }

    public boolean acceptMeet(Agent<Genotype> aAskingAgent) {
	    
		if(this._energy >= params.basicEnergyQuantum)
			return true;
		else
			return false;
	}

	public boolean acceptRepro(Agent<Genotype> aAskingAgent) {
	    
		if(this._energy > params.reproductionThreshold)
			return true;
		else
			return false;
	}

	protected void migr() {
	    
		double wantMigr = Math.random();
		
		if (wantMigr < params.migrationProbability) {
			
		    //jesli ma gdzie
			if(this._islands.size() > 1)
			{
				Island<Genotype> island = this.seekMigr();
				if(this._energy >= params.dieThreshold + params.basicEnergyQuantum) {
				    ElEmasAlgorithm.stat_numOfMigrations++;

					this.transferToEnv(params.basicEnergyQuantum);
					this.move(island);
					//System.out.println(this + " migracja na wyspe " + island);
				}
			}
		}
	}

	protected void meet() {

		if(this._homeIsland.getAgents().size() > 1) {
		    
		    double wantMeet = Math.random();
			if(wantMeet < 0.9) {
				if(this._energy >= params.basicEnergyQuantum)
				{
					Agent<Genotype> partner = this.seekMeet();
					if(partner.acceptMeet(this)) {
					    
						if(this.checkDomination(partner) == 1) {
							partner.transfer(this);
						} else if(this.checkDomination(partner) == -1) {
							this.transfer(partner);
						}
					}
				}
			}
		}
	}

	protected void repro() {

        if (this._homeIsland.getAgents().size() > 1
                && this._energy > params.reproductionThreshold) {
            
            double wantRepro = Math.random();
            if (wantRepro < 0.8) {
                Agent<Genotype> partner = this.seekRepro();
                if (partner.acceptRepro(this)) {
                    this.create(partner);
                }
            }
        }
    }

	public void LifeStep() {
		if(this._energy <= params.dieThreshold) {
			this.transferToEnv(this._energy);
			this.die();
		} else {
			this.migr();
			this.meet();
			this.repro();
		}
	}

	public void assimilateEnergy() {
		this._energy += params.basicEnergyQuantum;
	}

    public void assimilateEnergy(int amount) {
        this._energy += params.basicEnergyQuantum;
    }

    public void loseEnergy(int aAmont) {
		this._energy -= aAmont;
		assert this._energy >= 0;
	}

	public int getEnergy() {
		return this._energy;
	}

    @Override
    public Genotype getGenotype() {
        return this.genotype;
    }

    public List<Double> getValues() {
        return this.values;
    }

    @Override
    public double getValue(int index) {
        return this.values.get(index);
    }

    @Override
    public double getFitness(int index) {
        return this.fitnesses.get(index);
    }

    @Override
    public List<Double> getFitnesses() {
        return this.fitnesses;
    }

}